import format.amf.Value;
import format.flv.Data;
import hxvid.events.ConnectionEvent;
import hxvid.IApplication;
import hxvid.Client;
import hxvid.Logger;
import hxvid.Rtmp;
import hxvid.Commands;
import hxvid.Data;
import hxvid.Server;
import hxvid.SharedObject;

typedef PlayerData=
{
	var id:Int;
	var name:String;
	var x:Float;
	var y:Float;
	var z:Float;
	var pan:Float;
	var forward:Float;
	var rotation:Float;
	var health:Float;
}
typedef BulletData =
{
	var id : Int;
	var name : String;
	var x : Float;
	var y : Float;
	var rotation : Float;
	var health : Float;
}
typedef Players =
{
	var lock : neko.vm.Mutex;
	var data : Hash<PlayerData>;
}
typedef Bullets =
{
	var lock : neko.vm.Mutex;
	var data : Hash<BulletData>;
}
class Application implements IApplication
{
	public var config:ApplicationConfig;
	private var players:Players;
	private var bullets:Bullets;
	public var clients:Hash<Client>;
	
	private var server:Server;
	private var clientID:Int;
	private var logger:Logger;
	
	public function new(server:Server, config:ApplicationConfig)
	{
		this.server = server;
		this.config = config;
		
		this.clientID = 0;
		this.clients = new Hash();
		this.players = 
		{
			lock:new neko.vm.Mutex(),
			data:new Hash()
		};
		this.bullets = 
		{
			lock:new neko.vm.Mutex(),
			data:new Hash()
		};
		
		logger = new Logger(config.logFile);
		logger.id = "space3D/Application";
		logger.info("started");
		//server.addEventListener(ServerEvent.CLIENT_CONNECT, onBeforeConnect);
	}
	public function onConnect(client:Client, obj:ConnectionObject, ?arg:Dynamic=null):Void
	{
		logger.info("client:"+ client.ip);
		client.addEventListener(ConnectionEvent.CONNECTED, onConnected);
		client.addEventListener(ConnectionEvent.DISCONNECTED, onPlayerLeave);
		client.logger.enabled = false;
		client.addCommands(
		[
			["login", this.onPlayerJoin, T.Null, T.String ],
			["fire", this.onBulletJoin, T.Null, T.Object ],
			["playerMove", this.onPlayerMove, T.Null, T.Object ],
			["bulletMove", this.onBulletMove, T.Null, T.Object ],
			["bulletLeave", this.onBulletLeave, T.Null, T.Object ],
			["getPlayers", this.getPlayers, T.Null ],
			["getBullets", this.getBullets, T.Null ]
			
		]);
		client.accept();
	}
	function onConnected(event:ConnectionEvent):Void
	{
		logger.info("client:" + event.target.ip + " has received NetConnection.Connect.Success)");
	}
	function onPlayerJoin(cinf:CommandInfos, _ ,name:String):Void
	{	
		var client:Client = cinf.c;
		logger.info("client:"+client.ip +", with name:"+ name);
		
		if(clients.exists(name))
		{
			client.sendResult(cinf, false);
		}
		else
		{
			client.sendResult(cinf, true);
			client.id = clientID++;
			client.name = name;
			var player = 
			{
				id:client.id, 
				name:client.name,
				x:0.0,
				y:0.0,
				z:0.0,
				pan:0.0,
				forward:0.0,
				rotation:0.0,
				health:1.0
			}
			players.lock.acquire();
			players.data.set(name, player);
			for (c in clients)
			{
				c.send1("onPlayerJoin", player);
			}
			clients.set(name, client);
			players.lock.release();
		}
	}
	function onBulletJoin(cinf:CommandInfos, _, data:BulletData ):Void
	{
		bullets.lock.acquire();
		bullets.data.set(data.name, data);
		for (c in clients)
		{
			c.send1("onBulletJoin", data);
		}
		bullets.lock.release();
	}
	function getPlayers(cinf:CommandInfos, _ ):Void
	{
		var client:Client = cinf.c;
		logger.info("client:"+client.ip);
		var out:Array<PlayerData> = [];
		players.lock.acquire();
		for (p in players.data)
			out.push(p);
	//	logger.debug("out:"+out);
		client.sendResult(cinf, out);
		players.lock.release();
	}
	function getBullets(cinf:CommandInfos, _ ):Void
	{
		var client:Client = cinf.c;
		logger.info("client:"+client.ip);
		var out:Array<BulletData> = [];
		bullets.lock.acquire();
		for (b in bullets.data)
			out.push(b);
	//	logger.debug("out:"+out);
		client.sendResult(cinf, out);
		bullets.lock.release();
	}
	function onPlayerMove(cinf:CommandInfos, _, data:PlayerData ):Void
	{
		players.lock.acquire();
		players.data.set(data.name, data);
		for (c in clients)
		{
			c.send1("updatePlayer", data);
		}
		players.lock.release();
	}
	function hitTest(bullet:BulletData)
	{
		for(player in players.data)
			if(Math.sqrt( Math.pow(bullet.x - player.x, 2) + Math.pow(bullet.y - player.y, 2)) <= 25)
				return player;
		return null;
	}
	function onBulletMove(cinf:CommandInfos, _, data:PlayerData ):Void
	{
		bullets.lock.acquire();
		bullets.data.set(data.name, data);
		var player = hitTest(data);
		if(player!=null)
		{
			for (c in clients)
			{
				c.send2("onHit", data, player);
			}
		}
		else
		{
			for (c in clients)
			{
				c.send1("updateBullet", data);
			}
		}
		bullets.lock.release();
	}
	public function onPlayerLeave(event:ConnectionEvent)
	{
		var client = event.target;
		logger.info("client:" +client.ip+", id:"+client.id + ", name:" + client.name);
		clients.remove(client.name);
		bullets.lock.acquire();
		for(b in bullets.data)
		{
			if(b.name.split("-")[0] == client.name)
			{
				for (c in clients)
				{
					c.send1("onBulletLeave", b);
				}
				bullets.data.remove(b.name);
			}
		}
		if(!clients.iterator().hasNext())
		{
			bullets.data = new Hash();
		}
		bullets.lock.release();
		
		players.lock.acquire();
		var data = players.data.get(client.name);
		for (c in clients)
		{
			c.send1("onPlayerLeave", data);
		}
		players.data.remove(data.name);
		players.lock.release();
	}
	public function onBulletLeave(cinf:CommandInfos, _, data:BulletData ):Void
	{
		bullets.lock.acquire();
		for (c in clients)
		{
			c.send1("onBulletLeave", data);
		}
		bullets.data.remove(data.name);
		bullets.lock.release();
	}
}